#ifndef __INCtableh
#define __INCtableh

#include <iostream>
#include <map>
#include <iomanip>
#include <vector>
#include <string>
#include <algorithm> 
#include <type.h>

#ifdef TEXTTABLE_ENCODE_MULTIBYTE_STRINGS
#include <clocale>
#ifndef TEXTTABLE_USE_EN_US_UTF8
#define TEXTTABLE_USE_EN_US_UTF8
#endif
#endif

class TextTable
{

public:
    enum class Alignment
    {
        LEFT,
        RIGHT
    };
    typedef std::vector<std::string> Row;
    explicit TextTable(char horizontal,
                       char vertical,
                       char corner) : _horizontal(horizontal),
                                      _vertical(vertical),
                                      _corner(corner),
                                      _has_ruler(true),
                                      _tablename(NULLSTR)
    {
    }

    explicit TextTable() : _horizontal(EOFS),
                           _vertical(SPACE),
                           _corner(EOFS),
                           _has_ruler(false),
                           _tablename(NULLSTR)
    {
    }
    explicit TextTable(std::string name) : _horizontal(EOFS),
                                           _vertical(SPACE),
                                           _corner(EOFS),
                                           _has_ruler(false),
                                           _tablename(name)
    {
    }
    TextTable &setAlignment(unsigned i, Alignment alignment)
    {
        _alignment[i] = alignment;
        return (*this);
    }

    Alignment alignment(unsigned i) const
    {
        return _alignment[i];
    }

    char vertical() const
    {
        return _vertical;
    }

    char horizontal() const
    {
        return _horizontal;
    }
    size_t dataRowCount() const
    {
        if (columns() > 1)
        {
            return (columns() - 1);
        }
        return 0;
    }
    inline TextTable &addHeader(std::string const &content)
    {
        return add(content);
    }
    TextTable &add(std::string const &content)
    {
        _current.push_back(content);
        return (*this);
    }
    TextTable &setTableName(const char *name)
    {
        _tablename.clear();
        _tablename.append(std::string(name));
        return (*this);
    }
    std::string const &getTableName() const
    {
        return _tablename;
    }
    TextTable &endOfRow()
    {
        _rows.push_back(_current);
        _current.assign(0, "");
        if (_rows.size() == 1)
        {
            auto &iter = _rows[0];
            for (size_t i = 0; i < iter.size(); i++)
            {
                std::transform(iter[i].begin(), iter[i].end(),
                               iter[i].begin(), ::toupper);
            }
        }
        return (*this);
    }

    template <typename Iterator>
    TextTable &addRow(Iterator begin, Iterator end)
    {
        for (auto i = begin; i != end; ++i)
        {
            add(*i);
        }
        return endOfRow();
    }

    template <typename Container>
    TextTable &addRow(Container const &container)
    {
        return addRow(container.begin(), container.end());
    }

    std::vector<Row> const &rows() const
    {
        return _rows;
    }

    void setup() const
    {
        determineWidths();
        setupAlignment();
    }

    std::string ruler() const
    {
        std::string result;
        result += _corner;
        for (auto width = _width.begin(); width != _width.end(); ++width)
        {
            result += repeat(*width, _horizontal);
            result += _corner;
        }

        return result;
    }

    size_t width(unsigned i) const
    {
        return _width[i];
    }

    bool has_ruler() const { return _has_ruler; }

    int correctDistance(std::string string_to_correct) const
    {
        return static_cast<int>(string_to_correct.size()) -
               static_cast<int>(glyphLength(string_to_correct));
    }
    unsigned has_columns() const
    {
        return _rows.size() != 0;
    }

private:
    const char _horizontal;
    const char _vertical;
    const char _corner;
    const bool _has_ruler;
    Row _current;
    std::vector<Row> _rows;
    std::vector < std::size_t> mutable _width;
    std::vector<unsigned> mutable _utf8width;
    std::map<unsigned, Alignment> mutable _alignment;
    std::string _tablename;
    static std::string repeat(size_t times, char c)
    {
        std::string result;
        for (; times > 0; --times)
            result += c;

        return result;
    }
    std::size_t columns() const
    {
        return _rows[0].size();
    }
    std::size_t glyphLength(std::string s) const
    {
        std::size_t _byteLength = s.length();
#ifdef TEXTTABLE_ENCODE_MULTIBYTE_STRINGS
#ifdef TEXTTABLE_USE_EN_US_UTF8
        std::setlocale(LC_ALL, "en_US.utf8");
#else
#error You need to specify the encoding if the TextTable library uses multybyte string encoding!
#endif
        unsigned int u = 0;
        const char *c_str = s.c_str();
        unsigned _glyphLength = 0;
        while (u < _byteLength)
        {
            u += std::mblen(&c_str[u], _byteLength - u);
            _glyphLength += 1;
        }
        return _glyphLength;
#else
        return _byteLength;
#endif
    }

    void determineWidths() const
    {
        _width.assign(columns(), 0);
        _utf8width.assign(columns(), 0);
        for (auto rowIterator = _rows.begin();
             rowIterator != _rows.end();
             ++rowIterator)
        {
            Row const &row = *rowIterator;
            for (unsigned i = 0; i < row.size(); ++i)
            {
                _width[i] = (_width[i] > glyphLength(row[i]))
                                ? _width[i]
                                : glyphLength(row[i]);
            }
        }
    }

    void setupAlignment() const
    {
        for (unsigned i = 0; i < columns(); ++i)
        {
            if (_alignment.find(i) == _alignment.end())
            {
                _alignment[i] = Alignment::LEFT;
            }
        }
    }
};

inline std::ostream &operator<<(std::ostream &stream, TextTable const &table)
{
    table.setup();
    std::string name = table.getTableName();

    if (name.length() != 0)
    {
        std::transform(name.begin(), name.end(), name.begin(), ::toupper);
        stream << std::setw(int((table.ruler().length() + name.length()) / 2))
               << std::right << name << std::endl;
    }
    if (table.has_ruler())
    {
        stream << table.ruler() << std::endl;
    }
    for (auto rowIterator = table.rows().begin();
         rowIterator != table.rows().end();
         ++rowIterator)
    {
        TextTable::Row const &row = *rowIterator;
        stream << table.vertical();
        for (unsigned i = 0; i < row.size(); ++i)
        {
            auto alignment = (table.alignment(i) == TextTable::Alignment::LEFT)
                                 ? std::left
                                 : std::right;
            /*
            * std::setw( width ) works as follows: a string which goes in the 
            * stream with byte length (!) l is filled with n spaces 
            * so that l+n=width.
            * For a utf8 encoded string the glyph length g might
            *  be smaller than l. We need n spaces so that g+n=width 
            * which is equivalent to g+n+l-l=width ==> l+n = width+l-g
            * l-g (that means glyph length minus byte length) has to 
            * be added to the width argument.
            * l-g is computed by correctDistance.
            */
            stream << std::setw(int(table.width(i) + table.correctDistance(row[i])))
                   << alignment << row[i];
            stream << table.vertical();
        }
        stream << std::endl;
        if (table.has_ruler() && rowIterator == table.rows().begin())
        {
            stream << table.ruler() << std::endl;
        }
    }
    if (table.has_ruler())
    {
        stream << table.ruler() << std::endl;
    }
    return stream;
}

#endif